#include "ParticleSystem.h"
#include "Buffer.h"
#include "Texture.h"
#include "TextureResourceManager.h"
#include "NeroCore\NMemoryManager.h"

using namespace Graphics;

ParticleSystem::ParticleSystem(void)
	:mTexture(NULL),
	 mParticleList(NULL),
	 mVertices(NULL),
	 mVertexBuffer(NULL),
	 mIndexBuffer(NULL)
{
}

ParticleSystem::~ParticleSystem(void)
{
}

bool ParticleSystem::Initialize()
{
	mTexture = TextureResourceManager::GetInstance()->GetResource("Stars");

	bool result = InitializeParticleSystem();

	result = InitializeBuffers();

	return result;
}

void ParticleSystem::Deinitialize()
{
	DeinitializeBuffers();

	DeinitializeParticleSystem();
}

bool ParticleSystem::Update(float gameTime)
{
	KillParticles();

	EmitParticles(gameTime);

	UpdateParticles(gameTime);

	return UpdateBuffers();
}

void ParticleSystem::Render()
{
	RenderBuffers();
}

Texture* ParticleSystem::GetTexture()
{
	return mTexture;
}

bool ParticleSystem::InitializeParticleSystem()
{
	// Set the random deviation of where the particles can be located when emitted.
	mParticleDeviation.X = 0.5f;
	mParticleDeviation.Y = 0.1f;
	mParticleDeviation.Z = 2.0f;

	// Set the speed and speed variation of particles.
	mParticleVelocity = 1000.0f;
	mParticleVelocityVariation = 1.0f;

	// Set the physical size of the particles.
	mParticleSize = 0.2f;

	// Set the number of particles to emit per second.
	mParticlesPerSecond = 250.0f;

	// Set the maximum number of particles allowed in the particle system.
	mMaxParticles = 5000;

	// Create the particle list.
	mParticleList = new ParticleType[mMaxParticles];

	// Initialize the particle list.
	for(int i = 0; i < mMaxParticles; i++)
	{
		mParticleList[i].active = false;
	}

	// Initialize the current particle count to zero since none are emitted yet.
	mCurrentParticleCount = 0;

	// Clear the initial accumulated time for the particle per second emission rate.
	mAccumulatedTime = 0.0f;

	return true;
}

void ParticleSystem::DeinitializeParticleSystem()
{
	// Release the particle list.
	if(mParticleList)
	{
		MY_DELETE_ARRAY(mParticleList);
		mParticleList = NULL;
	}
}

bool ParticleSystem::InitializeBuffers()
{
	mVertexCount = mMaxParticles * 6;

	mIndexCount = mVertexCount;

	mVertices = MY_NEW(Memory::HID_Rendering,"Particle Verts") VertexType[mVertexCount];

	unsigned long* indices = MY_NEW(Memory::HID_Rendering, "Particles Indices") unsigned long[mIndexCount];

	// Initialize vertex array to zeros at first.
	memset(mVertices, 0, (sizeof(VertexType) * mVertexCount));

	// Initialize the index array.
	for(int i = 0; i < mIndexCount; i++)
	{
		indices[i] = i;
	}

	// Set up the description of the dynamic vertex buffer.
	D3D11_BUFFER_DESC vertexBufferDesc;
	vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	vertexBufferDesc.ByteWidth = sizeof(VertexType) * mVertexCount;
	vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	vertexBufferDesc.MiscFlags = 0;
	vertexBufferDesc.StructureByteStride = 0;

	// Give the subresource structure a pointer to the vertex data.
	D3D11_SUBRESOURCE_DATA vertexData;
	vertexData.pSysMem = mVertices;
	vertexData.SysMemPitch = 0;
	vertexData.SysMemSlicePitch = 0;

	// Now finally create the vertex buffer.
	mVertexBuffer = MY_NEW(Memory::HID_Rendering,"Particle System Vertices") Buffer();
	mVertexBuffer->Initialize(vertexBufferDesc, vertexData);

	// Set up the description of the static index buffer.
	D3D11_BUFFER_DESC indexBufferDesc;
	indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	indexBufferDesc.ByteWidth = sizeof(unsigned long) * mIndexCount;
	indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	indexBufferDesc.CPUAccessFlags = 0;
	indexBufferDesc.MiscFlags = 0;
	indexBufferDesc.StructureByteStride = 0;

	// Give the subresource structure a pointer to the index data.
	D3D11_SUBRESOURCE_DATA indexData;
	indexData.pSysMem = indices;
	indexData.SysMemPitch = 0;
	indexData.SysMemSlicePitch = 0;

	// Create the index buffer.
	mIndexBuffer = MY_NEW(Memory::HID_Rendering,"Particle System Indicies") Buffer();
	mIndexBuffer->Initialize(indexBufferDesc, indexData);

	// Release the index array since it is no longer needed.
	MY_DELETE_ARRAY(indices);
	indices = NULL;

	return true;
}

void ParticleSystem::DeinitializeBuffers()
{
	if(mIndexBuffer)
	{
		mIndexBuffer->Release();
		MY_DELETE(mIndexBuffer);
		mIndexBuffer = NULL;
	}

	if(mVertexBuffer)
	{
		mVertexBuffer->Release();
		MY_DELETE(mVertexBuffer);
		mVertexBuffer = NULL;
	}
}

void ParticleSystem::EmitParticles(float gameTime)
{
	mAccumulatedTime += gameTime;

	bool emitParticle = false;

	if(mAccumulatedTime > (1.0f / mParticlesPerSecond))
	{
		mAccumulatedTime = 0.0f;
		emitParticle = true;
	}

	float positionX, positionY, positionZ, velocity, red, green, blue;
	if(emitParticle && (mCurrentParticleCount < mMaxParticles - 1))
	{
		mCurrentParticleCount++;

		// Now generate the randomized particle properties.
		positionX = (((float)rand()-(float)rand())/RAND_MAX) * mParticleDeviation.X;
		positionY = (((float)rand()-(float)rand())/RAND_MAX) * mParticleDeviation.Y;
		positionZ = (((float)rand()-(float)rand())/RAND_MAX) * mParticleDeviation.Z;

		velocity = mParticleVelocity + (((float)rand()-(float)rand())/RAND_MAX) * mParticleVelocityVariation;

		red   = (((float)rand()-(float)rand())/RAND_MAX) + 0.5f;
		green = (((float)rand()-(float)rand())/RAND_MAX) + 0.5f;
		blue  = (((float)rand()-(float)rand())/RAND_MAX) + 0.5f;

		// Now since the particles need to be rendered from back to front for blending we have to sort the particle array.
		// We will sort using Z depth so we need to find where in the list the particle should be inserted.
		int index = 0;
		bool found = false;
		while(!found)
		{
			if((!mParticleList[index].active) || (mParticleList[index].positionZ < positionZ))
			{
				found = true;
			}
			else
			{
				index++;
			}
		}

		// Now that we know the location to insert into we need to copy the array over by one position from the index to make room for the new particle.
		int i = mCurrentParticleCount;
		int j = i - 1;

		while(i != index)
		{
			mParticleList[i].positionX = mParticleList[j].positionX;
			mParticleList[i].positionY = mParticleList[j].positionY;
			mParticleList[i].positionZ = mParticleList[j].positionZ;
			mParticleList[i].red       = mParticleList[j].red;
			mParticleList[i].green     = mParticleList[j].green;
			mParticleList[i].blue      = mParticleList[j].blue;
			mParticleList[i].velocity  = mParticleList[j].velocity;
			mParticleList[i].active    = mParticleList[j].active;
			i--;
			j--;
		}

		// Now insert it into the particle array in the correct depth order.
		mParticleList[index].positionX = positionX;
		mParticleList[index].positionY = positionY;
		mParticleList[index].positionZ = positionZ;
		mParticleList[index].red       = red;
		mParticleList[index].green     = green;
		mParticleList[index].blue      = blue;
		mParticleList[index].velocity  = velocity;
		mParticleList[index].active    = true;
	}
}

void ParticleSystem::UpdateParticles(float gameTime)
{
	// Each frame we update all the particles by making them move downwards using their position, velocity, and the frame time.
	for(int i = 0; i < mCurrentParticleCount; i++)
	{
		mParticleList[i].positionY = mParticleList[i].positionY - (mParticleList[i].velocity * gameTime * 0.001f);
	}
}

void ParticleSystem::KillParticles()
{
	// Kill all the particles that have gone below a certain height range.
	for(int i=0; i < mMaxParticles; i++)
	{
		if((mParticleList[i].active) && (mParticleList[i].positionY < -3.0f))
		{
			mParticleList[i].active = false;
			mCurrentParticleCount--;

			// Now shift all the live particles back up the array to erase the destroyed particle and keep the array sorted correctly.
			for(int j = i; j < mMaxParticles-1; j++)
			{
				mParticleList[j].positionX = mParticleList[j+1].positionX;
				mParticleList[j].positionY = mParticleList[j+1].positionY;
				mParticleList[j].positionZ = mParticleList[j+1].positionZ;
				mParticleList[j].red       = mParticleList[j+1].red;
				mParticleList[j].green     = mParticleList[j+1].green;
				mParticleList[j].blue      = mParticleList[j+1].blue;
				mParticleList[j].velocity  = mParticleList[j+1].velocity;
				mParticleList[j].active    = mParticleList[j+1].active;
			}
		}
	}
}

bool ParticleSystem::UpdateBuffers()
{
	VertexType* verticesPtr;

	Graphics::D3DDevice* nDevice = GetPlatform()->GetDevice()->As<Graphics::D3DDevice>();
	// Initialize vertex array to zeros at first.
	memset(mVertices, 0, (sizeof(VertexType) * mVertexCount));

	// Now build the vertex array from the particle list array.  Each particle is a quad made out of two triangles.
	int index = 0;

	for(int i=0; i < mCurrentParticleCount; i++)
	{
		// Bottom left.
		mVertices[index].position = D3DXVECTOR3(mParticleList[i].positionX - mParticleSize, mParticleList[i].positionY - mParticleSize, mParticleList[i].positionZ);
		mVertices[index].texture = D3DXVECTOR2(0.0f, 1.0f);
		mVertices[index].color = D3DXVECTOR4(mParticleList[i].red, mParticleList[i].green, mParticleList[i].blue, 1.0f);
		index++;

		// Top left.
		mVertices[index].position = D3DXVECTOR3(mParticleList[i].positionX - mParticleSize, mParticleList[i].positionY + mParticleSize, mParticleList[i].positionZ);
		mVertices[index].texture = D3DXVECTOR2(0.0f, 0.0f);
		mVertices[index].color = D3DXVECTOR4(mParticleList[i].red, mParticleList[i].green, mParticleList[i].blue, 1.0f);
		index++;

		// Bottom right.
		mVertices[index].position = D3DXVECTOR3(mParticleList[i].positionX + mParticleSize, mParticleList[i].positionY - mParticleSize, mParticleList[i].positionZ);
		mVertices[index].texture = D3DXVECTOR2(1.0f, 1.0f);
		mVertices[index].color = D3DXVECTOR4(mParticleList[i].red, mParticleList[i].green, mParticleList[i].blue, 1.0f);
		index++;

		// Bottom right.
		mVertices[index].position = D3DXVECTOR3(mParticleList[i].positionX + mParticleSize, mParticleList[i].positionY - mParticleSize, mParticleList[i].positionZ);
		mVertices[index].texture = D3DXVECTOR2(1.0f, 1.0f);
		mVertices[index].color = D3DXVECTOR4(mParticleList[i].red, mParticleList[i].green, mParticleList[i].blue, 1.0f);
		index++;

		// Top left.
		mVertices[index].position = D3DXVECTOR3(mParticleList[i].positionX - mParticleSize, mParticleList[i].positionY + mParticleSize, mParticleList[i].positionZ);
		mVertices[index].texture = D3DXVECTOR2(0.0f, 0.0f);
		mVertices[index].color = D3DXVECTOR4(mParticleList[i].red, mParticleList[i].green, mParticleList[i].blue, 1.0f);
		index++;

		// Top right.
		mVertices[index].position = D3DXVECTOR3(mParticleList[i].positionX + mParticleSize, mParticleList[i].positionY + mParticleSize, mParticleList[i].positionZ);
		mVertices[index].texture = D3DXVECTOR2(1.0f, 0.0f);
		mVertices[index].color = D3DXVECTOR4(mParticleList[i].red, mParticleList[i].green, mParticleList[i].blue, 1.0f);
		index++;
	}
	
	// Lock the vertex buffer.
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	HRESULT result = nDevice->GetDeviceContext()->Map(mVertexBuffer->GetResource(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the vertex buffer.
	verticesPtr = (VertexType*)mappedResource.pData;

	// Copy the data into the vertex buffer.
	memcpy(verticesPtr, (void*)mVertices, (sizeof(VertexType) * mVertexCount));

	// Unlock the vertex buffer.
	nDevice->GetDeviceContext()->Unmap(mVertexBuffer->GetResource(), 0);

	return true;
}

void ParticleSystem::RenderBuffers()
{
	Graphics::D3DDevice* nDevice = GetPlatform()->GetDevice()->As<Graphics::D3DDevice>();
	// Set vertex buffer stride and offset.
	unsigned int stride = sizeof(VertexType); 
	unsigned int offset = 0;
    
	// Set the vertex buffer to active in the input assembler so it can be rendered.
	ID3D11Buffer* vbuffer = mVertexBuffer->GetResource();
	nDevice->GetDeviceContext()->IASetVertexBuffers(0, 1, &vbuffer, &stride, &offset);

	// Set the index buffer to active in the input assembler so it can be rendered.
	nDevice->GetDeviceContext()->IASetIndexBuffer(mIndexBuffer->GetResource(), DXGI_FORMAT_R32_UINT, 0);

	// Set the type of primitive that should be rendered from this vertex buffer.
	nDevice->GetDeviceContext()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
}

int ParticleSystem::GetIndexCount()
{
	return mIndexCount;
}